#ifndef SOURCEFIELD_HPP
#define SOURCEFIELD_HPP

#include "meshTypes_fwd.hpp"
#include "solverBaseTypes_fwd.hpp"
#include "fieldBase.hpp"
#include "simulationTime.hpp"
#include "functionParserWrapper.hpp"
#include <functional>

/* only include that here, in sourceField.hpp */
#include "sourceField_traits.hpp"

namespace hms
{

enum class UpdateMethod {
	none,
	timeSeries,
	function, // not the best name, but better for import/export.
	callable
};

template<Uniformity _isUniform, typename MeshType>
class SourceField : public FieldBase<SourceField<_isUniform, MeshType>> {
public:
	using Type = SourceField<_isUniform, MeshType>;
	using ConstValueType = typename FieldTraits<Type>::ConstValueType;
	static constexpr Uniformity isUniform {_isUniform};

private:
	/* for time series, e.g. rain data */
	std::vector<std::pair<scalar,ArrayXXs>> m_timeSeries;
	/* for interpolation between time series data,
	 * or calculating values by function parser evaluation,
	 * or calculating values by callable updater */
	ArrayXXs m_values;
	/* function parsers to provide a simple (though not necessarily efficient)
	 * way of writing dynamic source fields */
	std::vector<FunctionParserWrapper> m_functions;
	/* functions require data for all three spatial dimensions */
	std::experimental::observer_ptr<const Field<MeshType>> m_zField {nullptr};

	/* external updater, e.g. for complex source terms such as turbulence,
	 * sediment transport,... No arguments allowed, so use structs or lambdas 
	 * to provide additional data. */
	std::function<void(ArrayXXs&)> m_updater;

	/* to update time series and functions, a reference to the simulation time
	 * is required */
	std::experimental::observer_ptr<const Time> m_time {nullptr};

	/* simulation time only moves forward, so both for interpolation and for
	 * getting the non-interpolated last values, the current index within
	 * m_values is stored */
	Index m_currentIndex {0};

	/* flags to differentiate between the kinds of source fields.
	 * by default, it is assumed, that only one value for the whole time is
	 * provided, so that no updating is needed, or that the values are updated
	 * completely externally. */
	UpdateMethod m_updateMethod {UpdateMethod::none};
	bool m_useInterpolation  {false};


public:
	SourceField() = default;

	/* ctor for static value (or completely external source calculation) */
	SourceField(
		const MeshBase<MeshType>&,
		ArrayXXs&&
	);

	/* ctor for time series */
	SourceField(
		const MeshBase<MeshType>&,
		std::vector<std::pair<scalar,ArrayXXs>>&&,
		const Time&,
		bool useInterpolation = false
	);

	/* ctor for calculation by function parser */
	SourceField(
		const MeshBase<MeshType>&,
		std::vector<FunctionParserWrapper>&&,
		const Time&,
		const Field<MeshType>& z = {}
	);

	/* ctor for calculation by callable updater */
	SourceField(
		const MeshBase<MeshType>&,
		std::function<void(ArrayXXs&)>&&,
		Index nComponents
	);

	/* we don't want to override, but overload */
	using FieldBase<Type>::values;

	/* in contrast to other FieldBase types, source fields don't provide a
	 * non-const reference to the stored values, so that they can only be set
	 * as a whole and not changed accidentally */
	/* for static values or external calculation (sets appropriate flags) */
	void values( ArrayXXs&& );

	/* for time series (sets appropriate flags) */
	void timeSeries( std::vector<std::pair<scalar,ArrayXXs>>&& );

	/* for function parsers (sets appropriate flags) */
	void functions( std::vector<FunctionParserWrapper>&& );

	/* for external updater callable (sets appropriate flags) */
	void callable(
		std::function<void(ArrayXXs&)>&&,
		Index nComponents
	);

	void time( const Time& );

	void zField( const Field<MeshType>& z );

	/* accessors */
	auto storage() const -> const ArrayXXs&;
	auto storage() -> ArrayXXs&;
	auto timeSeries() const
		-> const std::vector<std::pair<scalar,ArrayXXs>>&;

	auto functions() const -> const std::vector<FunctionParserWrapper>&;

	// auto updater() const -> const std::function<ArrayXXs()>&;

	auto time() const -> const Time&;

	auto updateMethod() const -> UpdateMethod;
	bool useInterpolation () const;
	void useInterpolation (bool);

	/* when the simulation time has advanced, call this function */
	void updateValues();
	
private:

	ConstValueType valuesImp() const;

	auto domainValuesImp() const -> decltype(auto);

	void updateValues_timeSeries();
	void updateValues_timeSeries_preventNesting();
	void updateValues_function();
	void updateValues_callable();

	auto zField() const -> const Field<MeshType>&;

	friend FieldBase<Type>;
};

} // end namespace hms

#include "sourceField.tpp"

#endif